//--------------------------------------------------------------------------------
/*!
\file	XMLDoc.cpp
\author	Ben Smith
\date	09/12/2006
\brief	This file contains the XMLDoc class which is used for XML IO.
\par	license
All content (c) 2005, DigiPen (USA) Corporation, all rights reserved.
*/
//--------------------------------------------------------------------------------

#include <fstream>
#include "XMLDoc.h"
#include "FileIOHelpers.h"

//--------------------------------------------------------------------------------
/*!
\brief	Constructor for XMLNode requires a name and can accept text data as well.
\author	Ben Smith
\param	name
	string name used for file I/O
\param	pData
	pointer to text string data.  This is an optional parameter.  If it is filled
	in, the node will assume ownership of the data (and try to delete it upon
	destruction.)
*/
//--------------------------------------------------------------------------------
XMLNode::XMLNode(const std::string& name, xmldataptr_t pData) : name_(name), pData_(pData)
{
}

//--------------------------------------------------------------------------------
/*!
\brief	Constructor for XMLNode requires a name and can accept text data as well.
\author	Ben Smith
\param	name
	String name used for file I/O
\param	str
	String data that will be copied and stored in the node.
*/
//--------------------------------------------------------------------------------
XMLNode::XMLNode(const std::string& name, const std::string& str) : name_(name)
{
	pData_ = xmldataptr_t(new std::string(str));
}

//--------------------------------------------------------------------------------
/*!
\brief	Destructor for XMLNode.
\author	Ben Smith
*/
//--------------------------------------------------------------------------------
XMLNode::~XMLNode()
{
}

//--------------------------------------------------------------------------------
/*!
\brief	Destructor for XMLNode.  Deletes allocated string data (if any)
\author	Ben Smith
\return
	Returns a pointer to the string data.  If no data exists, returns NULL.
*/
//--------------------------------------------------------------------------------
const xmldataptr_t XMLNode::GetData() const
{
	return pData_;
}

//--------------------------------------------------------------------------------
/*!
\brief	Sets the string data pointer inside the node.
		Note:: Setting a data pointer will change the expected file output of your
		node from:
			<name (attributes) />
				to
			<name (attributes)> data </name>
\author	Ben Smith
\param pData
	Pointer to string data to set into node.
*/
//--------------------------------------------------------------------------------
void XMLNode::SetData(xmldataptr_t pData)
{
	pData_ = pData;
}

//--------------------------------------------------------------------------------
/*!
\brief	Links a new child node onto the current node.
\author	Ben Smith
\param xmlIter
	An XML iterator type.  The node contained in the iterator MUST have a string
	name set.
*/
//--------------------------------------------------------------------------------
void XMLNode::Link(const xmliter_t& xmlIter)
{
	Link(xmlIter->second);
}

//--------------------------------------------------------------------------------
/*!
\brief	Links a new child node onto the current node.
\author	Ben Smith
\param pNode
	An XML node pointer.  The node pointed to MUST have a string name set.
*/
//--------------------------------------------------------------------------------
void XMLNode::Link(const xmlnodeptr_t pNode)
{
	children_.insert(std::make_pair(pNode->GetName(),pNode));
}

//--------------------------------------------------------------------------------
/*!
\brief	Adds a new attribute to the attribute list of a node.
\author	Ben Smith
\date 	09/16/2006
\param	attrib
	Attribute to add to the attribute list.
*/
//--------------------------------------------------------------------------------
void XMLNode::AddAttribute(const xmlattrib_t& attrib)
{
	attributes_[attrib.first] = xmlattribptr_t(new xmlattrib_t(attrib));
}

//--------------------------------------------------------------------------------
/*!
\brief	Method to output node hierarchy.  First outputs the current node as an
		XML tag and then recursively outputs its children.
\author	Ben Smith
\param os
	A reference to the stream we wish to output to.
\param pNode
	Pointer to XMLNode to output.
\return
	The same stream reference that we passed in.
*/
//--------------------------------------------------------------------------------
void WriteNode(std::ostream &os, const xmlnodeptr_t pNode, unsigned numTabs)
{
	//Insert tabs at beginning of line
	for (unsigned i=0; i < numTabs; ++i)
		os << "\t";

	//Output node tag opening
	os << "<" << pNode->name_ << " ";	// must contain space to account for attributes in the tag

	//Output all attributes
	for (xmlattribmap_t::const_iterator iter = pNode->attributes_.begin(), endIter = pNode->attributes_.end(); iter != endIter; ++iter)
		os << (*iter).second->first << "=\"" << (*iter).second->second << "\" ";

	//What kind of tag should this be?
	//Close off node output or recursively output the children
	if (pNode->pData_ || pNode->children_.size()) {
		os << ">" << std::endl;
		if (pNode->pData_)
			os << *pNode->pData_;
		if (pNode->children_.size()) {
			for (xmlconstiter_t iter = pNode->children_.begin(), endIter = pNode->children_.end(); iter != endIter; ++iter) {
				//os << std::endl;
				WriteNode(os, iter->second, numTabs+1);
				}
		}

		//Insert tabs at the end of the tag as well so that it is aligned with the opening tag
		for (unsigned i=0; i < numTabs; ++i)
			os << "\t";
		os << "</" << pNode->name_ << ">" << std::endl;
	}
	else {
		//Cap stand alone tag
		os << "/>" << std::endl;
	}
}

//--------------------------------------------------------------------------------
/*!
\brief	Reads in a node from a file.  Note that this is a recursive call and will
		read in and allocate space for an entire tree.  This function makes the
		assumption that we have already processed the leading angular bracket that
		defines the node.
\author	Ben Smith
\date	2006/09/14
\param	is
	Input stream to read node from.
\param	pNode
	Pointer to allocated node that should be read from file.
\return
	Returns false if the node was a comment.  Returns true otherwise.
*/
//--------------------------------------------------------------------------------
bool ReadNode(std::istream &is, xmlnodeptr_t pNode)
{
	//If it's a comment... skip it
	if (is.peek() == '!') {
		SkipToChar(is, '>');
		return false;
	}

	//Read node name
	char delimArray[] = {' ', '>'};
	ReadToChar(is, pNode->name_, delimArray, sizeof(delimArray));
	is.unget();

	//While we're inside the opening tag, read attributes
	bool readingAttributes = true;
	unsigned attribIter = 0;
	std::string attribStr;
	ReadToChar(is, attribStr, '>');
	if (attribStr.empty())
		readingAttributes = false;
	while (readingAttributes) {
		//Skip whitespace
		while (IsWhitespace(attribStr[attribIter]))
			   attribIter++;
		//Check for end open tag
		if (attribIter >= attribStr.size()){
			readingAttributes = false;
			continue;
		}
		//Check character
		char c = attribStr[attribIter];
		//Close node?
		if (c == '/' && attribIter == attribStr.size() - 1)
			return true;
		//Read attribute
		else {
			xmlattrib_t newAttribute; 
			attribIter += ReadToChar(attribStr, attribIter,newAttribute.first, '=');
			attribIter += SkipToChar(attribStr, attribIter, '"');
			attribIter += ReadToChar(attribStr, attribIter,newAttribute.second, '"');
			pNode->AddAttribute(newAttribute);
		}
	}

	//Now that we're outside the tag, we need to read data and then read children
	//Skip leading whitespace
	while (IsWhitespace(static_cast<char>(is.peek())))
		   is.get();
	//Read in data?
	std::string data;
	ReadToChar(is, data, '<');
	//If we read in data, allocate space for it
	if (data.size())
		pNode->pData_ = xmldataptr_t(new std::string(data));
	//Until we're closing the node
	while(is.peek() != '/') {
		xmlnodeptr_t newNode = xmlnodeptr_t(new XMLNode);
		if (ReadNode(is, newNode))
			pNode->Link(newNode);
		SkipToChar(is, '<');
	}
	return true;
}

//--------------------------------------------------------------------------------
/*!
\brief	Empty constructor for XMLTree.
\author	Ben Smith
\date	2006/09/13
*/
//--------------------------------------------------------------------------------
XMLTree::XMLTree()
{}

//--------------------------------------------------------------------------------
/*!
\brief	Constructs an XML tree from a node's children.
\author	Ben Smith
\date	2006/09/13
\param	pNode
	Pointer to node in which to construct tree from.
*/
//--------------------------------------------------------------------------------
XMLTree::XMLTree(const xmlnodeptr_t pNode)
{
	TreeFromNode(pNode);
}

//--------------------------------------------------------------------------------
/*!
\brief	Constructs an XMLTree from an xml iterator.  Uses the iterator's node's
		children.
\author	Ben Smith
\date	2006/09/13
\param	xmlIter
	XML iterator to construct tree from.
*/
//--------------------------------------------------------------------------------
XMLTree::XMLTree(const xmliter_t &xmlIter)
{
	TreeFromNode(xmlIter->second);
}

//--------------------------------------------------------------------------------
/*!
\brief	Tree destructor.  Recursively releases all 
\author	Ben Smith
\date	09/14/2006
*/
//--------------------------------------------------------------------------------
XMLTree::~XMLTree()
{	
}

//--------------------------------------------------------------------------------
/*!
\brief	Clears all nodes from a tree
\author	Ben Smith
\date	09/14/2006
*/
//--------------------------------------------------------------------------------
void XMLTree::Reset()
{
	tree_.clear();
}

//--------------------------------------------------------------------------------
/*!
\brief	Creates a tree from the children of an xml node.
\author	Ben Smith
\date	2006/09/13
\param	pNode
	Pointer to the node to construct tree with.
*/
//--------------------------------------------------------------------------------
void XMLTree::TreeFromNode(const xmlnodeptr_t pNode)
{
	tree_ = pNode->GetChildren();
}

//--------------------------------------------------------------------------------
/*!
\brief	Returns a range of nodes with names that match the tag parameter.  If no
		nodes can be found, both iterators in the range will point to the end of
		the tree.
\author	Ben Smith
\date	2006/09/13
\param	tag
	String tag to search for.
\return
	Returns a range of nodes that match the search criteria.
*/
//--------------------------------------------------------------------------------
xmlconstrange_t XMLTree::Find(const std::string& tag) const
{
	//Return bad range if cannot find
	xmlconstiter_t iter = tree_.find(tag);
	if (iter == tree_.end())
		return xmlconstrange_t(tree_.end(),tree_.end());

	return tree_.equal_range(tag);
}

//--------------------------------------------------------------------------------
/*!
\brief	Returns a range of nodes with names that match the tag parameter.  If no
nodes can be found, both iterators in the range will point to the end of
the current tree level.
\author	Ben Smith
\date	2006/09/13
\param	tag
String tag to search for.
\return
Returns a range of nodes that match the search criteria.
*/
//--------------------------------------------------------------------------------
xmlrange_t XMLTree::Find(const std::string& tag)
{
	//Return bad range if cannot find
	xmlconstiter_t iter = tree_.find(tag);
	if (iter == tree_.end())
		return xmlrange_t(tree_.end(),tree_.end());

	return tree_.equal_range(tag);
}

//--------------------------------------------------------------------------------
/*!
\brief	Returns an iterator that points to the beginning of the current tree level.
\author	Ben Smith
\date	2006/09/13
\return
	An iterator pointing to the beginning of the current tree level.
*/
//--------------------------------------------------------------------------------
xmlconstiter_t XMLTree::Begin() const
{
	return tree_.begin();
}

//--------------------------------------------------------------------------------
/*!
\brief	Returns an iterator that points to the end of the current tree level.
\author	Ben Smith
\date	2006/09/13
\return
An iterator pointing to the end of the current tree level.
*/
//--------------------------------------------------------------------------------
xmlconstiter_t XMLTree::End() const
{
	return tree_.end();
}

//--------------------------------------------------------------------------------
/*!
\brief	Returns an iterator that points to the beginning of the current tree level.
\author	Ben Smith
\date	2006/09/13
\return
An iterator pointing to the beginning of the current tree level.
*/
//--------------------------------------------------------------------------------
xmliter_t XMLTree::Begin()
{
	return tree_.begin();
}

//--------------------------------------------------------------------------------
/*!
\brief	Returns an iterator that points to the end of the current tree level.
\author	Ben Smith
\date	2006/09/13
\return
	An iterator pointing to the end of the current tree level.
*/
//--------------------------------------------------------------------------------
xmliter_t XMLTree::End()
{
	return tree_.end();
}

//--------------------------------------------------------------------------------
/*!
\brief	Redirects the tree structure by moving down a level into the iterator.
\author	Ben Smith
\date	2006/09/13
\param	xmlIter
	Iterator to branch into.
\return
	The same XMLTree class for chaining branch calls.
*/
//--------------------------------------------------------------------------------
XMLTree& XMLTree::Branch(const xmliter_t& xmlIter)
{
	TreeFromNode(xmlIter->second);
	return *this;
}

//--------------------------------------------------------------------------------
/*!
\brief	Redirects the tree structure by moving down a level into the node pointer.
\author	Ben Smith
\date	2006/09/13
\param	xmlIter
	Node pointer to branch into.
\return
	The same XMLTree class for chaining branch calls.
*/
//--------------------------------------------------------------------------------
XMLTree& XMLTree::Branch(const xmlnodeptr_t pNode)
{
	TreeFromNode(pNode);
	return *this;
}

//--------------------------------------------------------------------------------
/*!
\brief	Redirects the tree structure by moving down a level into the node pointer.
\author	Ben Smith
\date	09/19/2006
\param	tag
	String tag to search for nodes with.  If 1 or more nodes are found with this
	tag, uses the first in the range.
\param	errorCheck
	Optional bool pointer to capture any error (tags that cannot be found).
\return
	The same XMLTree class for chaining branch calls.
*/
//--------------------------------------------------------------------------------
XMLTree& XMLTree::Branch(const std::string& tag, bool* errorCheck)
{
	//Try to find a node associated with the tag
	xmlrange_t range = Find(tag);
	if (range.first != tree_.end())
		TreeFromNode(range.first->second);
	else
		if (errorCheck)
			*errorCheck = true;
	return *this;
}

//--------------------------------------------------------------------------------
/*!
\brief	Redirects the tree structure by moving down a level into the node pointer.
\author	Ben Smith
\date	09/19/2006
\param	tag
	String tag to search for nodes with.  If 1 or more nodes are found with this
	tag, uses the first in the range.	
\return
	true if call resulted in a branch (node was found) and false otherwise.
*/
//--------------------------------------------------------------------------------
/*bool XMLTree::Branch(const std::string& tag)
{
	//Try to find a node associated with the tag
	xmlrange_t range = Find(tag);
	if (range.first != tree_.end()) {
		TreeFromNode(range.first->second);
		return true;
	}
	return false;
}*/

//--------------------------------------------------------------------------------
/*!
\brief	Merges the current tree with nodes from another tree.
\author	Ben Smith
\date	2006/09/13
\param	pTree
	Tree to merge into current tree.
*/
//--------------------------------------------------------------------------------
void XMLTree::Link(const xmltreeptr_t pTree)
{
	tree_.insert(pTree->Begin(), pTree->End());
}

//--------------------------------------------------------------------------------
/*!
\brief	Inserts a node into the current tree.
\author	Ben Smith
\date	2006/09/13
\param	pNode
	Pointer to node to add into the current tree.
*/
//--------------------------------------------------------------------------------
void XMLTree::Link(const xmlnodeptr_t pNode)
{
	tree_.insert(make_pair(pNode->GetName(),pNode));
}

//--------------------------------------------------------------------------------
/*!
\brief	Inserts a node into the current tree.
\author	Ben Smith
\date	2006/09/13
\param	xmlIter
	Iterator that is pointing to node to insert into tree.
*/
//--------------------------------------------------------------------------------
void XMLTree::Link(const xmliter_t& xmlIter)
{
	Link(xmlIter->second);
}

//--------------------------------------------------------------------------------
/*!
\brief	Constructor for an XML document.
\author	Ben Smith
\date	09/14/2006
\param	name
	Name of the XML document.
*/
//--------------------------------------------------------------------------------
XMLDoc::XMLDoc(const std::string& name) : name_(name)
{

}

//--------------------------------------------------------------------------------
/*!
\brief	Constructor for an XML document.
\author	Ben Smith
\date	09/14/2006
\param	name
	Name of the XML document.
\param	fileName
	File name of the XML document.
*/
//--------------------------------------------------------------------------------
XMLDoc::XMLDoc(const std::string& name, const std::string& fileName) :
name_(name), fileName_(fileName)
{
	Parse(fileName);
}

//--------------------------------------------------------------------------------
/*!
\brief	Gets a copy of the XMLTree structure.
\author	Ben Smith
\date	09/19/2006
\param	rootLevel
	If true, the tree returned will be on the root level (where the root is the
	only visible node).  Otherwise, the tree returned will be the level below the
	root.
\return
	A copy of the XmlTree for the user to work with.
*/
//--------------------------------------------------------------------------------
XMLTree XMLDoc::GetTree(bool rootLevel) const
{
	if (rootLevel)
		return *xmlTree_;
	else
		return xmlTree_->Branch(xmlTree_->Begin());
}

//--------------------------------------------------------------------------------
/*!
\brief	Writes the current XML tree into a new document.
\author	Ben Smith
\date	09/14/2006
\param	fileName
	File name of XML document to create.
*/
//--------------------------------------------------------------------------------
void XMLDoc::Write(const std::string& fileName)
{
	assert(xmlTree_->Size() && "XML Document is empty");
	assert(xmlTree_->Size() == 1 && "XML Documents has more than 1 root node");

	//Open up a new file stream
	std::ofstream output(fileName.c_str());

	//Write meta data
	output << "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>" << std::endl;

	//Write all nodes into XML document
	for (xmlconstiter_t iter = xmlTree_->Begin(), endIter = xmlTree_->End(); iter != endIter; ++iter)
		WriteNode(output, iter->second, 0);

	//Close file stream
	output.close();
}

//--------------------------------------------------------------------------------
/*!
\brief	Reads the data from an XML file into a new XML tree.
\author	Ben Smith
\date	09/14/2006
\param	fileName
	XML document to read in.
*/
//--------------------------------------------------------------------------------
void XMLDoc::Parse(const std::string& fileName)
{
	//If there's no tree... make a new one
	if (!xmlTree_)
		xmlTree_.reset(new XMLTree);
	xmlTree_->Reset();

	//Open up a new file stream
	std::ifstream input(fileName.c_str());
	assert(input.is_open() && "Cannot open file for reading");

	//Read nodes into XML document
	while (SkipToCharInFile(input, '<')) {
		//Skip meta data
		if (input.peek() == '?')
			continue;
		//Read in node
		xmlnodeptr_t pNode(new XMLNode);
		if (ReadNode(input, pNode))
			xmlTree_->Link(pNode);
	}

	//Close file stream
	input.close();
}